//
//  DWLogManager.m
//  OCAPPDemo
//
//  Created by OS on 2022/1/10.
//

#import "DWLogManager.h"
#import "FCFileManager.h"

NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
NSString * const UncaughtExceptionHandlerFileKey = @"UncaughtExceptionHandlerFileKey";

// 记录之前的Crash回调函数（如果有的话）
static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;


/// 日志格式控制
@interface DWLogFileForrmatter : DDLogFileFormatterDefault

@end


@implementation DWLogManager

+ (instancetype)sharedManager {
    static id instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    
    return instance;
}

#pragma mark - 日志写入

+ (void)startDDLog {
    
    [DDLog addLogger:[DDOSLogger sharedInstance]]; // Uses os_log
    [[DDOSLogger sharedInstance] setLogFormatter:[[DWLogFileForrmatter alloc] init]];

    DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
    fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    fileLogger.logFormatter = [[DWLogFileForrmatter alloc] init];
    [DDLog addLogger:fileLogger];
    
}

+ (void)logError:(NSString *)string {
    [self log:string level:DDLogLevelError];
}

+ (void)logWarn:(NSString *)string {
    [self log:string level:DDLogLevelWarning];
}

+ (void)logInfo:(NSString *)string {
    [self log:string level:DDLogLevelInfo];
}

+ (void)logDebug:(NSString *)string {
    [self log:string level:DDLogLevelDebug];
}

+ (void)logVerbose:(NSString *)string {
    [self log:string level:DDLogLevelVerbose];
}

+ (void)log:(NSString *)string level:(DDLogLevel)level {
    
    if (![DWLogManager sharedManager].openLog) {
        return;
    }
    
    switch (level) {
        case DDLogLevelError:
            DDLogError(@"%@", string);
            break;
        case DDLogLevelWarning:
            DDLogWarn(@"%@", string);
            break;
        case DDLogLevelInfo:
            DDLogInfo(@"%@", string);
            break;
        case DDLogLevelDebug:
            DDLogDebug(@"%@", string);
            break;
        case DDLogLevelVerbose:
        case DDLogLevelAll:
            DDLogVerbose(@"%@", string);
            break;
        default:
            break;
    }
}

#pragma mark - 崩溃监听

/// 开始监听崩溃
+ (void)startCrash {
    
    if (![DWLogManager sharedManager].openLog) {
        return;
    }
    
    // OC NSException
    [DWLogManager registerUncaughtExceptionHandler];
    // Swift Signal
    registerSignalHandler();
    
}

/// 信号异常处理方法
/// @param exception 异常信息
- (void)handleException:(NSException *)exception {
    
    NSDictionary *userInfo = exception.userInfo;
    NSArray *callStackSymbols = [userInfo objectForKey:UncaughtExceptionHandlerAddressesKey];
    // 保存Crash日志到沙盒cache目录
    [DWLogManager writeCrashLog:exception callStackSymbols:callStackSymbols];

}

/// 注册信号回调
void registerSignalHandler(void) {
    
    signal(SIGSEGV, handleSignalException);
    signal(SIGFPE, handleSignalException);
    signal(SIGBUS, handleSignalException);
    signal(SIGPIPE, handleSignalException);
    signal(SIGHUP, handleSignalException);
    signal(SIGINT, handleSignalException);
    signal(SIGQUIT, handleSignalException);
    signal(SIGABRT, handleSignalException);
    signal(SIGILL, handleSignalException);
    signal(SIGTRAP, handleSignalException);
    
}


/// 信号回调方法
/// @param sig 信号
void handleSignalException(int sig) {
  
    /* 获取堆栈信息的方式
    NSMutableString *crashString = [[NSMutableString alloc]init];
    void* callstack[128];
    int i, frames = backtrace(callstack, 128);
    char** traceChar = backtrace_symbols(callstack, frames);
    for (i = 0; i <frames; ++i) {
        [crashString appendFormat:@"%s\n", traceChar[i]];
    }
    NSArray<NSString *> * callStacks = [crashString componentsSeparatedByString:@"\n"];
    */
    
    NSArray<NSString *> *callStacks = [NSThread callStackSymbols];
    
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:sig] forKey:UncaughtExceptionHandlerSignalKey];
    [userInfo setObject:callStacks forKey:UncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:@"SigCrash" forKey:UncaughtExceptionHandlerFileKey];
    
    NSString *reason = [NSString stringWithFormat:@"Signal %d was raised.\n", sig];
    
    NSException *exception = [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason:reason userInfo:[userInfo copy]];
    
    [[DWLogManager sharedManager] performSelectorOnMainThread:@selector(handleException:)
                                                   withObject:exception
                                                waitUntilDone:YES];
    
    NSSetUncaughtExceptionHandler(nil);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    signal(SIGHUP, SIG_DFL);
    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGTRAP, SIG_DFL);
    
}

+ (void)registerUncaughtExceptionHandler {
    // 将别人之前注册的Crash回调取出并备份
    previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    // 然后再注册自己的
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}

// 崩溃时的回调函数
static void UncaughtExceptionHandler(NSException * exception) {
    // 保存Crash日志到沙盒cache目录
    [DWLogManager writeCrashLog:exception callStackSymbols:exception.callStackSymbols];
    // 在自己handler处理完后记得把别人的handler注册回去，形成规范的SOP
    if (previousUncaughtExceptionHandler) {
        previousUncaughtExceptionHandler(exception);
    }
    // 杀掉程序，这样可以防止同时抛出的SIGABRT被Signal异常捕获
    kill(getpid(), SIGKILL);
}


+ (void)writeCrashLog:(NSException *)exception callStackSymbols:(NSArray <NSString *>*)callStackSymbols {
    
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *appVersion = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
    NSString *appBuild = [infoDictionary objectForKey:@"CFBundleVersion"];
    NSString* systemVersion = [[UIDevice currentDevice] systemVersion];
    
    DWCrashLogModel *crashLog = [DWCrashLogModel new];
    crashLog.appVer = appVersion;
    crashLog.buildVer = appBuild;
    crashLog.systemVer = systemVersion;
    crashLog.name = name;
    crashLog.reason = reason;
    crashLog.message = callStackSymbols;
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd_HH:mm:ss.SSS";
    crashLog.crateDate = [formatter stringFromDate:[NSDate date]];
    
    NSString *filename = [NSString stringWithFormat:@"%@.log", crashLog.crateDate];
    NSString *filepath = [[DWLogManager getCrashPath] stringByAppendingPathComponent:filename];
    [FCFileManager createFileAtPath:filepath withContent:[crashLog description]];
}


#pragma mark - 路径获取

+ (NSString *)getCrashPath {
    NSString *path = @"Crash/";
    path = [[FCFileManager pathForCachesDirectory] stringByAppendingPathComponent:path];
    [FCFileManager createDirectoriesForPath:path];
    return path;
}

+ (NSString *)getLogPath {
    NSString *path = @"Logs/";
    path = [[FCFileManager pathForCachesDirectory] stringByAppendingPathComponent:path];
    [FCFileManager createDirectoriesForPath:path];
    return path;
}

@end


/// 崩溃模型
@implementation DWCrashLogModel

- (NSString *)description {
    NSMutableString *ms = [NSMutableString new];
    for (NSString *str in _message) {
        [ms appendFormat:@"\n%@；", str];
    }
    return [NSString stringWithFormat:
            @"crateDate: %@ \nappVer: %@\nbuildVer: %@ \nsystemVer: %@ \nname: %@ \nreason: %@ \nmessage: %@ \n",
            _crateDate,
            _appVer,
            _buildVer,
            _systemVer,
            _name,
            _reason,
            ms.copy];
}

@end




/// 日志格式控制
@implementation DWLogFileForrmatter

- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
    NSString *level = [self levelStringWith:logMessage->_flag];
    return [NSString stringWithFormat:@"%@(%@) %@ %@[%zd]\n%@\n", logMessage->_threadName, logMessage->_threadID, level, logMessage->_fileName, logMessage->_line, logMessage->_message];
}

- (NSString *)levelStringWith:(DDLogFlag)level {
    NSString *res = @"A";
    switch (level) {
        case DDLogFlagVerbose:
            res = @"V";
            break;
        case DDLogFlagInfo:
            res = @"I";
            break;
        case DDLogFlagError:
            res = @"E";
            break;
        case DDLogFlagDebug:
            res = @"D";
            break;
        case DDLogFlagWarning:
            res = @"W";
            break;
        default:
            break;
    }
    return res;
}

@end

